use crate::tysys::{IntType, UserTypeId, ValueType};

/// Initial representation of a module with types resolved but layouts still
/// undecided.  This is how we decide if a stencil hierarchy is sensible and
/// figure out general constraints.
#[derive(Clone, Debug)]
pub struct ModuleDef {
    entries: Vec<(String, Entry)>,
}

impl ModuleDef {
    pub fn new(entries: Vec<(String, Entry)>) -> Self {
        Self { entries }
    }

    pub fn entries(&self) -> &[(String, Entry)] {
        &self.entries
    }

    pub fn find_entry_by_name(&self, s: &str) -> Option<&Entry> {
        self.entries
            .iter()
            .find(|(name, _)| name == s)
            .map(|(_, e)| e)
    }

    pub fn find_entry_by_type_id(&self, id: UserTypeId) -> Option<&Entry> {
        self.entries
            .iter()
            .map(|(_, e)| e)
            .find(|e| e.user_type_id() == Some(id))
    }

    pub fn find_type_id_by_name(&self, s: &str) -> Option<UserTypeId> {
        self.entries
            .iter()
            .find(|(name, _)| name == s)
            .and_then(|(_, e)| e.user_type_id())
    }
}

#[derive(Clone, Debug)]
pub enum Entry {
    Struct(UserTypeId, StructDef),
}

impl Entry {
    pub fn user_type_id(&self) -> Option<UserTypeId> {
        match self {
            Self::Struct(id, _) => Some(*id),
        }
    }
}

#[derive(Clone, Debug)]
pub struct StructDef {
    entries: Vec<StructEntry>,
}

impl StructDef {
    pub fn new(entries: Vec<StructEntry>) -> Self {
        Self { entries }
    }

    pub fn entries(&self) -> &[StructEntry] {
        &self.entries
    }

    pub fn find_field_by_name(&self, s: &str) -> Option<&StructFieldDef> {
        self.entries
            .iter()
            .filter_map(|e| match e {
                StructEntry::Field(d) => Some(d),
                _ => None,
            })
            .find(|d| d.name == s)
    }
}

#[derive(Clone, Debug)]
pub enum StructEntry {
    /// A concrete struct field.
    Field(StructFieldDef),

    /// Zero padding of some number of bytes, not addressible.
    Pad(u8),

    /// Padding up to a fixed offset.
    PadTo(usize),
}

#[derive(Clone, Debug)]
pub struct StructFieldDef {
    name: String,
    ty: FieldType,
}

impl StructFieldDef {
    pub fn new(name: String, ty: FieldType) -> Self {
        Self { name, ty }
    }

    pub fn name(&self) -> &str {
        &self.name
    }

    pub fn ty(&self) -> &FieldType {
        &self.ty
    }
}
